%%%---------------------------------------------------
%%% ECT: preprocessor for Object Oriented Erlang
%%% Written by Gábor Fehér, 2008 at BUTE
%%%---------------------------------------------------
%%% cctrans2 - OO transformation for client modules
%%%
%%% client module: does not defines a class, but uses them
%%%
%%% This module performs the same functionality as cctrans2, it's 
%%% the client module transformer, which can be invoked 
%%% thorough classtrans. This module itself can be compiled with 
%%% classtrans, which can invoke this module or cctrans2 for 
%%% client transofrmation.
%%%
%%% Features:
%%%   * pattern matching of objects
%%%
%%%        #classname{a = 1, b = X, ...} = A
%%%
%%%      extends 
%%%
%%%        #recordname{a = 1, b  = X, ...} = A
%%%
%%%      with the possibility that A can be any subclass of classname.
%%%      Also invokes O(1) runtime type checking. This feature can also
%%%      be used in clause heads, and bodies, but not in guards
%%%
%%%   * object attribute modification
%%%
%%%       A = B#classname{a = 1, b = 2, ...}
%%%
%%%     extends
%%%
%%%       A = B#recordname{a = 1, b = 2, ...}
%%%
%%%    with the possibility that B can be a subclass of classname. In this case,
%%%    the type of A will be the type of B. (and not classname) This feature
%%%    can only be used in function bodies.
%%%
%%%
%%%   * method call
%%%      {A}:methodname(Param1, Param2, ...) translates to element(1, A):mtehodname(A, Param1, Param2, ...)
%%%                                                  or to begin X = A, element(1, X):methodname(X, Param1, Param2, ...)
%%%                                                     if X is not a variable
%%%      {{A}}:methodname(Param1, Param2, ...) translates to a an expression, which invokes
%%%                                            the method named methodname in the superclass of the calling class,
%%%                                            but with the same parameters as in the previou example.
%%%

%%%
%%% This transformation is implemented by subclassing idtrans2, which is 
%%% the slightly modified version of factory erlidtrans2, but with object 
%%% state variable passed thorough function calls. The intermediate class
%%% basetrans provides some basic, problem-independent functionality for 
%%% the transformation. (Currently only variable name gerneration.)
%%%
%%% The subclassing means in practice, that only those functions 
%%% and clauses are implemented in this class, that should behave
%%% differently than the erlidtrans2 ones; and the others stay in 
%%% idtrans2.

-include_lib("ect/include/ect.hrl").

-class(cctrans2).
-import_class(idtrans2).
?SUPERCLASS(idtrans2).
-version(1).
?FIELDS(1, public, {
     acounter = 0,
     objpats = [],
               %% list of collected class-subpatterns in a pattern, in the form of {SubPattern, NewVarName} where
               %% NewVarName is the name of the variable which was substituted in the place of the subpattern

     boundvars = [],   %% list of the names of bound variables
                       %%  - maintained thorough traversing
                       %%  - duplications might occur
                       %% always append new elements at the beginning of the list! parallel_exprs/2 relies on this.

     guard_substitute_vars,  %% The flattened list of patterns. When a variable is found in a function guard,
                                 %% which was in a pattern in the head, the corresponding expression from this list is 
                                 %% substituted in the place of that variable.
     postponed_guard_checks = [],
     guard_expr_level = 0,
     fun_shadow_active = false,
    
     records,  %% list of known record definitions
     classes,  %% list of known class definitions
     class_def,
     thisclass_fieldmap_av,
     boundvar_checks = false,
                      %% if true, then each variable in an expression checked if it is bound, and an error is 
                      %% issued if it is unbound
     force_boundvar_checks = false,
     upgrade_mode = mode1
    }).

?METHODS([
    clauses/2, icr_clauses/2, fun_clauses/2,
    process_clause_shadows/6,
    clause/2,
    ectv_matches/4,
    expr/2,
    expr_objpats/2,
    forms/2,
    gexpr/2,
    pattern/2,
    parallel_exprs/2,
    parse_transform/3,
    unique_atom/2,
    unique_atom/1,
    unique_atom_list/2,
    attach_guard_checks/2
]).

-export([parse_transform_module/3, parse_transform_class/3]).

-include("objpats.hrl").
-include("common_defs.hrl").

unique_atom(This = #cctrans2{acounter = AC}, Prefix) ->
    {
         This#cctrans2{acounter = AC+1},
         list_to_atom(Prefix++integer_to_list(AC))
    }.
%% Returns an unique atom, that can be used as a variable name.
%% Uses a counter in This.
unique_atom(This) ->
    {This}:unique_atom("_CCTRANS_").

unique_atom_list(This, N) when N =:= 0 ->
    {This, []};
unique_atom_list(This1, N) when N > 0 ->
    {This2, Atom2} = unique_atom(This1),
    {This3, List} = unique_atom_list(This2, N-1),
    {This3, [Atom2|List]}.

%% Entry point to transform a module that is a class.
parse_transform_class(
    Forms,
    #compile_info{
        class_definition = Definition,
        classes = Classes,
        fieldmap_av = FieldMapAV,
        upgrade_mode = UpgradeMode
    },
    Options) ->

    This = #cctrans2{
                class_def = Definition,
                classes = Classes,
                thisclass_fieldmap_av = FieldMapAV,
                upgrade_mode = UpgradeMode},
    {_, X} = {This}:parse_transform(Forms, Options),
    X.

%% Entry point to transform a module that is not a class
parse_transform_module(Forms, Options, Classes) ->
    This = #cctrans2{class_def = #class{name = []}, classes = Classes},
    {_, X} = {This}:parse_transform(Forms, Options),
    X.

%% 
parse_transform(This1, Forms, Options) ->
    Records = records:scan(Forms),
    This2 = This1#cctrans2{
                records = Records,
                force_boundvar_checks = lists:member(force_boundvar_checks, Options)
                },
    my_pp:printforms(".stage1.ectgen.erl", Forms, Options),
    Res = {This3, X} = {This2}:forms(Forms),
    my_pp:printforms(".stage2.ectgen.erl", X, Options),

    #cctrans2{postponed_guard_checks = []} = This3,    %FIXME implement all cases
    %io:format("~p~n", [Forms]),
    Res.


forms(This1, [F0|Fs0]) ->
    try {This1}:form(F0) of
    {This2, F1} ->
        {This3, Fs1} = {This2}:forms(Fs0),
        {This3, [F1|Fs1]}
    catch
        throw:{translateError, Line, ErrorMessage} ->
            {This1, [{error, {Line, erl_parse, ErrorMessage}}]};
        error:function_clause ->
            StackTrace = erlang:get_stacktrace(),
            [{idtrans2, Function, [_, SecondParam|_]}|_] = StackTrace,
            % get line number from secon param
            Line = element(2, SecondParam),
            ErrorMessage = io_lib:format("[ect] [idtrans2] illegal ~p", [Function]),
            {This1, [{error, {Line, erl_parse, ErrorMessage}}]}
    end;
forms(This, []) -> {This, []}.

% function clauses
clauses(This1, [C0|Cs]) ->
    {This2, C1} = {This1#cctrans2{boundvars = []}}:clause(C0),
    {This3, Cs1} = {This2}:clauses(Cs),
    {This3, [C1|Cs1]};
clauses(This, []) -> {This, []}.

% fun clauses
fun_clauses(This1, [C0|Cs]) ->
    BoundVars = This1#cctrans2.boundvars,
    This2 = This1#cctrans2{fun_shadow_active = true},
    {This3, C1} = {This2}:clause(C0),
    This4 = This3#cctrans2{boundvars = BoundVars},
    {This5, Cs1} = {This4}:fun_clauses(Cs),
    {This5, [C1|Cs1]};
fun_clauses(This, []) -> {This, []}.

% other clauses
icr_clauses(This1, [C0|Cs]) ->
    BoundVars = This1#cctrans2.boundvars,
    {This2, C1} = {This1}:clause(C0),
    This3 = if Cs =/= [] -> This2#cctrans2{boundvars = BoundVars};
               true -> This2
            end,
    {This4, Cs1} = {This3}:icr_clauses(Cs),
    {This4, [C1|Cs1]};
icr_clauses(This, []) -> {This, []}.


process_clause_shadows(This, false, _, NBV, G, B) ->
    {This, G, B};
process_clause_shadows(This1, true, OutsideBoundVars, NewBoundVars1, G0, B0) ->
    case ordsets:intersection(ordsets:from_list(OutsideBoundVars), ordsets:from_list(NewBoundVars1)) of
    [] ->
        {This1, G0, B0};
    ShadowedVars when is_list(ShadowedVars) ->
        {This2, SVarNames} = {This1}:unique_atom_list(length(ShadowedVars)),
        
        Renames = lists:zip(ShadowedVars, SVarNames),
        G1 = fast_transform:rename_variables(Renames, G0),
        B1 = fast_transform:rename_variables(Renames, B0),
        {This2, G1, B1}
        %Line = unknown,
        %throw({translateError, Line, io_lib:format("[ect] error: shadowing variables in fun head: ~p. This is not yet supported.", [ShadowedVars])})
    end.


%% %% Collects record definitions in field records.
%% form(This1 = #cctrans2{records = Records1}, {attribute,Line,record,{Name,Defs0}}) ->
%%     {This2, Defs1} = {This1}:record_defs(Defs0),
%%     RecDef = {attribute,Line,record,{Name,Defs1}},
%%     {This2#cctrans2{records = [records:form_to_record(RecDef)| Records1 ]},
%%      RecDef
%%     };
%% form(This, Form) ->
%%     {{This}}:form(Form).

%% Transforms a clause containing OO syntax. The tricky part here is the
%% head, if it cointains OO style patterns.
%%
clause(This1 = #cctrans2{records = Records, classes = Classes, objpats = [], class_def = #class{name = ThisClass}, fun_shadow_active = Shadow}, {clause,Line,H0,G0,B0}) ->
    This2 = This1#cctrans2{objpats = [], guard_substitute_vars = [], fun_shadow_active = false},
    {This3 = #cctrans2{objpats = Objs}, H1} = {This2}:head(H0),
    %% Now head is converted, but OO syntax patterns are just substituted with 
    %% variables. The variable-pattern list is in objpats.

    #cctrans2{boundvars = BoundVars} = This3,  

    %% flatten all the OO patterns we collected in head, in the meantime maintaining classinfo cache (Classes1, Classes2)
    Flat = objpats:relax_list(Objs, Records, Classes, ThisClass, normal),

    %% split the flat patterns into Guard-type, and Expression-type components
    %%  This depends on the already bound vars. If we are in a fun (Shadow = true), then its
    %%  head does not see bound vars from the outside.
    BoundVars2 = 
        case Shadow of
        true -> [];
        false -> BoundVars
        end,
    {Guards, HExprs} = objpats:split(Flat, BoundVars2),
    NewBoundVars0 = objpats:extract_boundvars(Flat),  % new bound variables as a result of _OOP matches_
    % TODO doc
    This4 = This3, G1 = G0, B1 = B0, NewBoundVars = NewBoundVars0,
    
    %% Process guards. guard_substitue_vars is used to substitute those variable refernces in the guard,
    %% which reflects to variables in OO patterns. These must be substituted with the corresponding 
    %% flattened expressions.
    This5 = This4#cctrans2{objpats = [], guard_substitute_vars = Flat, boundvars = NewBoundVars++BoundVars},
    {This6, G2} = {This5}:guard(G1),
    
    %% Process body, no special operations needed, a prefix of 
    %% commands will be inserted at the beginning, to create variables
    %% originally created in head.
    This7 = This6#cctrans2{guard_substitute_vars = []},
    
    {This8, B2} = {This7}:exprs(B1),
    
    %% Create guard commands, for those OO head patterns, that
    %% assigns the same variable a differnet value. These different
    %% values must be checked for equality in the guard, since tha 
    %% OO pattern matches are transformed into separate value assingments.
    EQGuards = objpats:equalities(HExprs),
    
    %% append the new guard list to the original one, with the andalso operator
    NewGuards = Guards++EQGuards,
    G3 = if 
            NewGuards =:= [] ->
                G2;
            G2 =:= [] ->
                [NewGuards];
            true ->
                lists:map(fun(Item) -> NewGuards++Item end, G2)
        end,

    B3 = HExprs++B2,
    %B3 = B2,
    
    % Change names of those shadowed variables, that were assigned by ECT at the beginnig
    % of the body. (If they are shadowed again in a sub-tuple then those instances are
    % already renamed, so they will not be bothered here.)
    {This9, G4, B4} = {This8}:process_clause_shadows(Shadow, BoundVars, NewBoundVars0, G3, B3),
    
    {This9, {clause,Line,H1,G4,B4}}.

%% - Record all variables into boundvars.
%% - Substitute all object type patterns with a variable, and record them as {Pattern,VarName} in objpats.
pattern(This = #cctrans2{boundvars = BoundVars}, {var,Line,V}) ->
    {This#cctrans2{boundvars = [V|BoundVars]},
     {var,Line,V}};
pattern(This1 = #cctrans2{objpats = Objs, classes = Classes, boundvars = BoundVars}, Pattern = {record,Line,Name,_Pfs}) ->
    case classes:is_class(Classes, Name) of
    true ->
        {This2, NewVarName} = {This1}:unique_atom(), 
        NewVar = {var,Line,NewVarName},
        {This2#cctrans2{ objpats = [{Pattern, NewVarName}|Objs], boundvars = [NewVarName|BoundVars] },
         NewVar
        };
    _ ->
        {{This1}}:pattern(Pattern)
    end;
pattern(This, Pat) ->
    {{This}}:pattern(Pat).

%%
%% List of expressions that does not affect each other> variables may be bound
%% but they are not readable in other expressions, only after this block.
%% For example: A#obj{c = Z = 5, b = Z} invalid
%%              A#obj{c = Z = 5, b = 6} valid
%% This function processes the list of expressions between the curly braces.
%% If an invalid case is found, an error message is generated. This is 
%% necessary for object field updates, because those are not checked by
%% the compiler.
%%
parallel_exprs(This1, [E0|Es]) ->
    BoundVars = This1#cctrans2.boundvars,
    {This2, E1} = {This1}:expr(E0),
    
    NewBoundVars = This2#cctrans2.boundvars,
    Len = length(NewBoundVars)-length(BoundVars),
    AddedBoundVars = lists:sublist(NewBoundVars, 1, Len),
    
    % remove added bound vars
    This3 = This2#cctrans2{boundvars = BoundVars, boundvar_checks = true},
    {This4, Es1} = {This3}:parallel_exprs(Es),
    % restore added bound vars
    %This5 = This4#cctrans2{boundvars = AddedBoundVars++This3#cctrans2.boundvars, boundvar_checks = false},  %FIXME ellenőrizni miért csak adatsturktúra-váltásnál jött ki ez a hiba. talán lightbuild?
    This5 = This4#cctrans2{boundvars = AddedBoundVars++This4#cctrans2.boundvars, boundvar_checks = false},
    
    {This5, [E1|Es1]};
parallel_exprs(This, []) ->
    {This, []}.

%% Converts the OO patterns in This.objpats into runtime type-checks and value extractions.
%%
%% called in function exprs
%%
expr_objpats(This1 = #cctrans2{objpats = ObjList, records = Records, classes = Classes, class_def = #class{name = ThisClass}}, E) ->
    Line = element(2, E),  %%extract line number from any type of abstract syn. tree expression
    {This2, VarName} = {This1}:unique_atom(),
    %% flatten the collected object patterns, but only in shallow mode (not recursing into sub-patterns)
    FlatList = objpats:relax_list(ObjList, Records, Classes, ThisClass, shallow),
    Exprs0 = objpats:resolve_list(FlatList, match),
    This25 = This2#cctrans2{objpats = []},
    %% parse the created expressions recursively, because the variable substitution and shallow mode flattening
    %% avoided their contents
    {This3, Exprs} = {This25}:exprs(Exprs0),
    {This3#cctrans2{objpats = []},
     [{match, Line, {var, Line, VarName}, E} | Exprs  ]++[{var, Line, VarName}]
    }.

ectv_matches(This1, OldObj, [{match, Line1, {atom, _, Field}, Pattern1}|Matches1], VFieldDefs) ->
    {This2, Matches2} = {This1}:ectv_matches(OldObj, Matches1, VFieldDefs),

    {This3, Pattern2} = {This2}:pattern(Pattern1),
    #class_field{name = Field, global_pos = GlobalPos, local_pos = LocalPos} = lists:keyfind(Field, 2, VFieldDefs),
    {This3,
        [{match, Line1,
            Pattern2,
            ast:element(
                LocalPos
                ,ast:element(GlobalPos, OldObj, Line1)
                ,Line1)
         }
        |Matches2
        ]
    };
ectv_matches(This1, _, [], _) ->
    {This1, []}.

%% virtual ECT functions for version handling
expr(This1 = #cctrans2{thisclass_fieldmap_av = FieldMapAV, class_def = Def}, {call,Line,{atom,_,ectv_match},As0}) ->
    [{integer, _, Version}
    ,{tuple, _, MatchList}
    ,OldObj = {var, _, _}
    ] = As0,
    {Version, VFieldDefs} = lists:keyfind(Version, 1, FieldMapAV),
    {This2, MatchExprs1} = {This1}:ectv_matches(OldObj, MatchList, VFieldDefs),
    MatchExprs2 =
        [{match, Line,
            {atom, Line, true},
            classes:type_and_version_check(Def, Version, OldObj, Line)
         }
        |MatchExprs1]++[{atom, Line, ok}],
    {This2, {block, Line, MatchExprs2}};
expr(This1 = #cctrans2{class_def = Definition}, {call,Line,{atom,_,ectv_get_local_version},As0}) ->
    [OldObj = {var, _, _}] = As0,
    {This1,
        {'if', Line,
            [{clause, Line, 
                [],
                [[ast:class_typecheck_guard(Definition, OldObj, Line)]],
                [classes:get_local_version(Definition, OldObj, Line)]
            }]
        }
    };

%% do boundness check if boundvar_checks flag is true
expr(This = #cctrans2{boundvar_checks = BVC, force_boundvar_checks = FBVC, boundvars = BoundVars}, {var,Line,V})
    when BVC =:= true orelse FBVC =:= true ->
    case lists:member(V, BoundVars) of
    true -> {This, {var,Line,V}};
    false -> throw({translateError, Line, io_lib:format("[ect] unbound variable ~p, list of boundvars: ~p", [V, BoundVars])})
    end;
%% call method of superclass object
expr(This1 = #cctrans2{class_def = Def = #class{super_class = {ok, SuperClass}}, upgrade_mode = UpgradeMode},
        {call,Line, {remote,_,{tuple,_,[{tuple,_,[Obj]}]},Function}, Args}) ->
    SuperModule = 
        case UpgradeMode of
        mode1 ->
            % proxy module of the superclass is known at compile time (it has no version info)
            {atom, Line, SuperClass};
        mode2 ->
            % get the name of proxy module of superclass at run time, from the object:
            %  (this is necessary, because proxy module names include global version numbers)
            % get the third element of the next to last sub-tuple:
            ast:element(3, 
                ast:element(
                    tuple_size(Def#class.class_types) + ?FIELD_GLOBAL_STARTPOS - 2,
                    Obj,
                    Line),
                Line)
        end,
    {This2, Args2} = {This1}:expr_list(Args),
    {This2, 
     {call,Line,
      {remote,Line, SuperModule, Function},
      [Obj|Args2]
     }
    };
%%
%% call method of object, when the object is a variable
%%
expr(This1, {call,Line1,{remote,Line2,{tuple,Line3,[Obj = {var, _, _}]},Fun},Args1}) ->
    {This2, Args2} = {This1}:expr_list(Args1),
    {This2,
     {call,Line1,
      {remote,Line2,{call,Line3,{atom,Line3,element},[{integer,Line3,1},Obj]},Fun},
      [Obj|Args2]}
    };
%%
%% call a method of object, when the objects is a result of an expression
%%
expr(This1, {call,Line1,
                {remote,Line2,{tuple,Line3,[Obj]},Fun},
                Args1}) ->

    {This2, Args2} = {This1}:expr_list(Args1),
    {This3, VarName} = {This2}:unique_atom(),
    {This4, Obj2} = {This3}:expr(Obj),
    {This5, Expr2} = {This4}:expr({call,Line1, {remote,Line2,{tuple,Line3,[{var,Line3,VarName}]},Fun}, Args2}),
    {
        This5,
        {block, Line1,
            [{match, Line1, {var, Line1, VarName}, Obj2}
            ,Expr2
            ]
        }
    };
%% object field query expression
expr(This1 = #cctrans2{classes = Classes, class_def = #class{name = ThisClass}}, Expression = {record_field,Line,Rec0,Name,{atom, _, Field0}}) ->
    case classes:lookup(Classes, Name) of
    notfound ->
    	{{This1}}:expr(Expression);
    Definition ->
		{This2, RecVarName} = {This1}:unique_atom(),
		RecVar = {var, Line, RecVarName},
		{This3, Rec1} = {This2}:expr(Rec0),
		{This3,
		 	{block, Line,
				[{match, Line, RecVar, Rec1}
                ,ast:class_typecheck(Definition, RecVar, Line)
                ,classes:field_accessor(Definition, Field0, RecVar, ThisClass, Line)
                ]
            }
        }
    end;
%% object field manipulation expression
expr(This1 = #cctrans2{classes = Classes, class_def = #class{name = ThisClass}}, Expression = {record,Line,Rec0,Name,Upds0}) ->
    case classes:lookup(Classes, Name) of
    notfound ->
        {{This1}}:expr(Expression);
    Definition ->
        {This2, TypeCheckExprs1, ParallelExprs1, CleanExprs} =
            objexprs:translate_update(This1,
                                       fun(This) -> {This}:unique_atom() end,
                                       Rec0,
                                       Upds0,
                                       Definition,
                                       ThisClass,
                                       Line
                                     ),
        
        {This3, TypeCheckExprs2} = {This2}:exprs(TypeCheckExprs1),
        %io:format("BoundVars before parexps: ~p~n", [This3#cctrans2.boundvars]),
        {This4, ParallelExprs2} = {This3}:parallel_exprs(ParallelExprs1),
        %io:format("BoundVars after parexps: ~p~n", [This4#cctrans2.boundvars]),
        {This4,
            {block, Line, TypeCheckExprs2++ParallelExprs2++CleanExprs}
        }
    end;
expr(This1 = #cctrans2{classes = Classes}, E = {record,Line,Name,Inits0}) ->
	% convert init to an update of the result of the class:new() function.
	case classes:is_class(Classes, Name) of
	true ->
		{This1}:expr( {record,Line, ast:rcall(Name, new, [], Line), Name, Inits0} );
	_ ->
		{{This1}}:expr(E)
	end;
	%{{This1}}:expr({record,Line,Name,Inits0});
%% Pattern-match
expr(This1, {match,Line,P0,E0}) ->
    {This2, E1} = {This1}:expr(E0),
    {This3, P1} = {This2}:pattern(P0),
    case This3#cctrans2.objpats of
    [] ->
        % there were no OOP patterns in P0
        %  no conversion is required, make more simple output
        {This3, {match,Line,P1,E1}};
    _ ->
        % OOP patterns of P0 are collected in This.objpats
        %  -a list of expressions will do these matches
        PE = {match, Line, P1, E1},
        {This4, Exprs} = {This3}:expr_objpats(PE),
        {This4,
            {block, Line, Exprs}
%             {'try', Line, Exprs,
%                 [],
%                 [],
%                 []
%             }   hát ez nem jött be
        }
    end;
expr(This, Expr) ->
    {{This}}:expr(Expr).


gexpr(This1 = #cctrans2{guard_expr_level = 0}, GExpr1) ->
    This2 = This1#cctrans2{guard_expr_level = 1},
    {This3, GExpr2} = {This2}:gexpr(GExpr1),
    {This4, GExpr3} = {This3}:attach_guard_checks(GExpr2),
    This5 = This4#cctrans2{guard_expr_level = 0},
    {This5, GExpr3};
%% Substitute Vars with a corresponding expressions,
%% from guard_substitute_vars, which is a list
%% of flatitems.
gexpr(This = #cctrans2{guard_substitute_vars = GSV}, Var = {var,_,_}) -> 
    {This, gexpr_substitute(GSV, Var)};
gexpr(This = #cctrans2{postponed_guard_checks = Checks, classes = Classes, class_def = #class{name = ThisClass}}, GExpr = {record_field,Line,Rec0,Name,Field0}) ->
    case classes:lookup(Classes, Name) of
    notfound ->
        {{This}}:gexpr(GExpr);
    Definition ->
        {atom,_,Field} = Field0,
        This2 = This#cctrans2{postponed_guard_checks = [ast:class_typecheck_guard(Definition, Rec0, Line)|Checks]},
        {This2, classes:field_accessor(Definition, Field, Rec0, ThisClass, Line)}
    end;
% places where postponed typechecks should be inserted
gexpr(This1, GExpr1 = {op,Line,Op,L0,R0}) ->
    case erl_internal:bool_op(Op, 2) orelse Op =:= 'orelse' orelse Op =:= 'andalso' of
    true ->
        {This2, L1} = {This1}:gexpr(L0),
        {This3, L2} = {This2}:attach_guard_checks(L1),
        {This4, R1} = {This3}:gexpr(R0),
        {This5, R2} = {This4}:attach_guard_checks(R1),
        {This5, {op,Line,Op,L2,R2}};
    _ -> {{This1}}:gexpr(GExpr1)
    end;
gexpr(This1, GExpr1 = {op,Line,Op,A0}) ->
    case erl_internal:bool_op(Op, 1) of
    true ->
        {This2, A1} = {This1}:gexpr(A0),
        {This3, A2} = {This2}:attach_guard_checks(A1),
        {This3, {op,Line,Op,A2}};
    _ -> {{This1}}:gexpr(GExpr1)
    end;
gexpr(This, Expr) ->
    {{This}}:gexpr(Expr).

attach_guard_checks(This = #cctrans2{postponed_guard_checks = [Check|Checks]}, GExpr) ->
    Line = element(2, GExpr),
    This2 = This#cctrans2{postponed_guard_checks = Checks},
    {This2}:attach_guard_checks({op, Line, 'and', {op, Line, 'orelse', Check, {atom, Line, 'fail'}}, GExpr});
attach_guard_checks(This = #cctrans2{postponed_guard_checks = []}, GExpr) ->
    {This, GExpr}.
    
    
gexpr_substitute([], Var) ->
    Var;
gexpr_substitute([#flatitem{type = eq, arg = {var,_,VarName1}, expr = Expr}|_], {var,_,VarName2}) when VarName1 =:= VarName2 ->
    Expr;
gexpr_substitute([_|Fs], Var) ->
    gexpr_substitute(Fs, Var).
